package edu.kit.aifb.ldstream.learner.tasks.mod;


import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;

import org.openrdf.model.BNode;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFParser.DatatypeHandling;


import edu.kit.aifb.ldstream.env.Environment;
import edu.kit.aifb.ldstream.learner.out.n3.PropertyDistribN3Converter;
import edu.kit.aifb.ldstream.learner.query.InstanceCache;
import edu.kit.aifb.ldstream.learner.query.OccurrenceQuery;
import edu.kit.aifb.ldstream.stat.distrib.IDistribution;
import edu.kit.aifb.ldstream.stat.util.IDistributionChecker;

public class PropDistribTask extends LearnerTask{

	private Set<Type> typeSet;
	private OccurrenceQuery occQuery;
	private boolean inferencing;
	private IDistributionChecker octave;
	private ModStatistics stats;
	
	private int numProps = 0;
	private int itdProp = 0;

	private PropertyDistribN3Converter writer;

	public PropDistribTask(Set<Type> typeSet, boolean infer, IDistributionChecker octave, ModStatistics stats){
		this.typeSet = typeSet;
		this.writer = new PropertyDistribN3Converter();
		this.inferencing = infer;
		this.octave = octave;
		this.stats = stats;
		this.occQuery = new OccurrenceQuery();
	}



	@Override
	public void run() {
		if(Environment.LOGGING){
			Environment.log.info("Started to learn PropertyDistributions.");
		}

		startup();
		setParserConfig();

		learn();

		double linkageDegree = ((double) this.itdProp) / ((double) this.numProps) ;
		this.stats.setLinkageDegree(linkageDegree);
		if(Environment.LOGGING){
			Environment.log.info("LinkageDegree: " + linkageDegree);
		}
		
		if(Environment.LOGGING){
			Environment.log.info("All PropertyDistributions learned.");
		}
		this.occQuery.close();
		this.writer.close();
		shutdown();

	}


	private void learn(){
		for(Type type : this.typeSet){
			
			// saving the instance distribution of this type to the file
			// has nothing to do with this method but leads to a better readability
			this.writer.storeInstanceOccurence(type);
			
			if(Environment.LOGGING) Environment.log.info("Learning PropertyDistribution for " + type.stringValue());
			
			InstanceCache ic = new InstanceCache(type);
			//save the instance names
			this.writer.storeInstanceName(type, ic.iterator());
			Iterator<URI> iter = ic.iterator();
			// every type gets its own map for counting the predicate occurrences
			Map<Property, LinkedList<Count>> occ = new TreeMap<Property, LinkedList<Count>>();
			URI instance;
			while(iter.hasNext()){
				instance = iter.next();
				// crawl this particular instance
				crawl(instance, occ, type.getNamespace());

				// after an instance is crawled we append a new count for the next instance
				if(iter.hasNext()){
					for(Property pred : occ.keySet()){
						occ.get(pred).add(new Count());
					}
				}
			}

			// at this point we crawled every instance of the type
			// compute distribution & save the results
			computeDistribution(occ, type, ic);
			if(Environment.LOGGING) Environment.log.info("PropertyDistribution for " + type.stringValue() + " successfully learned.");

			// free memory
			occ = null;
			ic.close();
			System.gc();
		}
	}

	private void computeDistribution(Map<Property, LinkedList<Count>> occ, Type type, InstanceCache ic){
		if(Environment.log.isLoggable(Level.FINEST)) Environment.log.finest("Entering computeDistribution for Type " + type.stringValue());
		Iterator<URI> iter;
		int instanceOCC;
		Integer[]  iArray;
		LinkedList<Count> currentList;
		IDistribution propDistrib;

		for(Property prop : occ.keySet()){

			int i = 0;
			iter = ic.iterator();
			currentList = occ.get(prop);
			iArray = new Integer[currentList.size()];

			for(Count c : currentList){
				instanceOCC = this.occQuery.countOccurrence(iter.next(), prop.getName());
				if(instanceOCC != 0){
					iArray[i++] = Integer.valueOf(c.count / instanceOCC);
				} else{
					iArray[i++] = Integer.valueOf(0);
				}
			}
			propDistrib = this.octave.discrete(iArray, false);
			iArray = null;

			// save the result !!
			this.writer.storePropToFile(type, prop, propDistrib);
		}
		if(Environment.log.isLoggable(Level.FINEST)) Environment.log.finest("Leaving computeDistribution for Type " + type.stringValue());
	}

	


	private void crawl(Resource subject, Map<Property, LinkedList<Count>> occ, String typeNameSpace){

		RepositoryResult<Statement> result = null;
		try {
			//result = this.con.getStatements(subject, null, null, this.inferencing, Environment.STREAM_CONTEXT_URI);
			result = this.con.getStatements(subject, null, null, this.inferencing);
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		try{
			try{
				Statement s;
				Property pred;
				while(result.hasNext()){
					s = result.next();
					if(!s.getPredicate().equals(RDF.TYPE)){
						pred = new Property(s.getPredicate());
						if(!occ.containsKey(pred)){
							LinkedList<Count> ll = new LinkedList<Count>();
							ll.add(new Count());
							occ.put(pred, ll);

							if(s.getObject() instanceof BNode){
								pred.setBNode();
							} else{
								if(s.getObject() instanceof URI){
									URI obj = (URI) s.getObject();
									if(typeNameSpace.length() < obj.getNamespace().length()){
										if(!obj.getNamespace().contains(typeNameSpace)){
											pred.setInterdomain();
										}
									}else{
										if(!typeNameSpace.contains(obj.getNamespace())){
											pred.setInterdomain();
										}
									}
								}
							}
						}
						this.numProps++;
						if(pred.isInterdomain()){
							this.itdProp++;
						}
						occ.get(pred).getLast().increment();

						if(s.getObject() instanceof BNode){
							crawl((BNode) s.getObject(), occ, typeNameSpace);
						}
					}
				}
			}finally{
				result.close();
			}
		} catch(RepositoryException e){
			e.printStackTrace();
		}
		
	}


	private void setParserConfig(){
		this.con.setParserConfig(new ParserConfig(true, true, true, DatatypeHandling.VERIFY));
	}


}
